use std::str::FromStr;

use actix_http::StatusCode;
use actix_web::{web, HttpResponse};
use serde_json::Value;
use wither::bson::{doc, oid::ObjectId};

use crate::{
    errors::cons_error,
    middlewares::authorize::{club_getters::default_getter, Authorize},
    models::{
        invite::InviteTokenModelDTO,
        user::{NormalUserModel, UserModelDTO, UserRole},
    },
    services::invite_service::InviteService,
    utils::{
        authorize::check_user_has_permission,
        db_utils::{build_filter, COMMON_NO_REGEXP},
        path_params::PathParamMDB,
    },
    AppError, AppResult, PaginationQuery,
};
mod ctrl_struct {
    use chrono::{DateTime, Utc};
    use serde::{Deserialize, Serialize};

    use crate::models::invite::InviteTokenType;
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct InviteUserToClubCtrlReq {
        pub user_id: String,
    }
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct CreateInviteTokenCtrlBodyReq {
        pub max_count: u64,
        pub expired_time: DateTime<Utc>,
        pub invite_type: InviteTokenType,
    }
}

/// invite_user_to_club
/// 邀请用户加入社团
async fn invite_user_to_club(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    path_params: PathParamMDB,
    req: web::Query<ctrl_struct::InviteUserToClubCtrlReq>,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    let target_id = ObjectId::from_str(&req.user_id)?;

    // 检查是否拥有对社团人员的操作权限
    if !check_user_has_permission(
        &user.clone().into(),
        club_id.clone().to_string(),
        UserRole::LeaderOrAdmin as u8,
    ) {
        return Err(AppError::Status(
            StatusCode::UNAUTHORIZED,
            cons_error::MSG_REQUIRE_AUTHORIZE.into(),
        ));
    }
    if !invite_service
        .check_user_in_club_invite_token(club_id, user.id, None)
        .await?
    {
        return Err(AppError::Status(
            StatusCode::UNAUTHORIZED,
            cons_error::MSG_USER_SHOULD_IN_INVITE_TOKEN.into(),
        ));
    }

    let res = invite_service
        .invite_user_to_club(user, target_id, club_id)
        .await?;

    Ok(HttpResponse::Ok().json(UserModelDTO::from(res)))
}
async fn create_invite_token(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    body_req: web::Json<ctrl_struct::CreateInviteTokenCtrlBodyReq>,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);

    let token = invite_service
        .create_invite_token(
            user,
            body_req.max_count,
            body_req.expired_time.to_owned(),
            body_req.invite_type,
            club_id,
        )
        .await?;

    Ok(HttpResponse::Ok().json(InviteTokenModelDTO::from(token)))
}

async fn find_all_invite_tokens_in_club(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    query: web::Query<PaginationQuery>,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    if !check_user_has_permission(
        &user.into(),
        club_id.clone().to_string(),
        UserRole::LeaderOrAdmin as u8,
    ) {
        return Err(AppError::Status(
            StatusCode::UNAUTHORIZED,
            cons_error::MSG_REQUIRE_AUTHORIZE.into(),
        ));
    }
    let filter = doc! {
        "club_id":club_id
    };
    let res = invite_service
        .find_all_invite_tokens(filter, &query)
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<InviteTokenModelDTO>()))
}

async fn search_all_invite_tokens_in_club(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    where_value: web::Json<Option<Value>>,
    query: web::Query<PaginationQuery>,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let club_id = get_path_param!(path_params, club_id, MSG_REQUIRE_CLUB_ID_IN_PATH);
    if !check_user_has_permission(
        &user.into(),
        club_id.clone().to_string(),
        UserRole::LeaderOrAdmin as u8,
    ) {
        return Err(AppError::Status(
            StatusCode::UNAUTHORIZED,
            cons_error::MSG_REQUIRE_AUTHORIZE.into(),
        ));
    }
    let mut filter = build_filter(
        where_value.as_ref().unwrap_or(&Value::Null),
        &COMMON_NO_REGEXP,
    )
    .unwrap_or_default();
    filter.insert("club_id", club_id);
    let res = invite_service
        .find_all_invite_tokens(filter, &query)
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<InviteTokenModelDTO>()))
}

async fn delete_invite_token(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let invite_id = get_path_param!(path_params, invite_id, MSG_REQUIRE_INVITE_ID_IN_PATH);
    let invite = invite_service
        .find_one_invite_token(invite_id, None)
        .await?;
    if !check_user_has_permission(
        &user.into(),
        invite.club_id.clone().to_string(),
        UserRole::LeaderOrAdmin as u8,
    ) {
        return Err(AppError::Status(
            StatusCode::UNAUTHORIZED,
            cons_error::MSG_REQUIRE_AUTHORIZE.into(),
        ));
    }
    invite_service.delete_invite_token(invite_id).await?;
    Ok(HttpResponse::NoContent().finish())
}

async fn find_one_invite_token(
    invite_service: web::Data<InviteService>,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let invite_id = get_path_param!(path_params, invite_id, MSG_REQUIRE_INVITE_ID_IN_PATH);
    let invite = invite_service
        .find_one_invite_token(invite_id, None)
        .await?;
    Ok(HttpResponse::Ok().json(InviteTokenModelDTO::from(invite)))
}

async fn find_all_invite_tokens(
    invite_service: web::Data<InviteService>,
    query: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let res = invite_service.find_all_invite_tokens(None, &query).await?;
    Ok(HttpResponse::Ok().json(res.map_into::<InviteTokenModelDTO>()))
}

async fn search_all_invite_tokens(
    invite_service: web::Data<InviteService>,
    where_value: web::Json<Option<Value>>,
    query: web::Query<PaginationQuery>,
) -> AppResult<HttpResponse> {
    let filter = build_filter(
        where_value.as_ref().unwrap_or(&Value::Null),
        &COMMON_NO_REGEXP,
    )
    .unwrap_or_default();

    let res = invite_service
        .find_all_invite_tokens(filter, &query)
        .await?;
    Ok(HttpResponse::Ok().json(res.map_into::<InviteTokenModelDTO>()))
}

async fn use_invite_token(
    invite_service: web::Data<InviteService>,
    user: NormalUserModel,
    path_params: PathParamMDB,
) -> AppResult<HttpResponse> {
    let invite_id = get_path_param!(path_params, invite_id, MSG_REQUIRE_INVITE_ID_IN_PATH);
    let res = invite_service
        .add_user_to_invite_token(invite_id, user)
        .await?;
    Ok(HttpResponse::Ok().json(InviteTokenModelDTO::from(res)))
}

pub fn configure_invite_controller(config: &mut web::ServiceConfig) {
    config
        .service(web::resource("/do/club/{club_id}").route(web::post().to(invite_user_to_club)))
        .service(web::resource("/use/{invite_id}").route(web::post().to(use_invite_token)))
        .service(
            web::resource("/club/{club_id}")
                .route(web::post().to(create_invite_token))
                .route(web::get().to(find_all_invite_tokens_in_club)),
        )
        .service(
            web::resource("/club/{club_id}/search")
                .route(web::post().to(search_all_invite_tokens_in_club)),
        )
        .service(
            web::resource("/single/{invite_id}")
                .route(web::get().to(find_one_invite_token))
                .route(web::delete().to(delete_invite_token)),
        )
        .service(
            web::resource("")
                .route(web::get().to(find_all_invite_tokens))
                .wrap(Authorize::new(UserRole::Admin, default_getter)),
        )
        .service(
            web::resource("/search")
                .route(web::post().to(search_all_invite_tokens))
                .wrap(Authorize::new(UserRole::Admin, default_getter)),
        );
}
